package com.a0soft.gphone.aTruffleHog.srvc;
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 3, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * Copyright(C) 2011 
 *   Sam Lu <ysl.sam.lu (at) gmail.com>
 *
 */

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.util.LinkedList;
import java.util.zip.CRC32;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;

import com.a0soft.gphone.aTruffleHog.util.AESEncryptor;
import com.a0soft.gphone.aTruffleHog.util.CONFIGURATION;
import com.a0soft.gphone.aTruffleHog.util.CONSTS;
import com.a0soft.gphone.aTruffleHog.util.HttpCacheMgr;
import com.a0soft.gphone.aTruffleHog.util.aDebug;

import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;

public class HttpConnSrvc extends IntentService {
	public static final String ACTION_DOWNLOAD_STATUS = "com.a0soft.gphone.aTruffleHog.ActionDownloadStatus";
    public static final String EXTRA_DOWNLOAD_CONTENT = "dc"; //string

    public static final String EXTRA_DOWNLOAD_STATUS = "ds";  //DOWNLOAD_STATUS_XXX
    public static final int DOWNLOAD_STATUS_UNKNOWN         = 0;
    public static final int DOWNLOAD_STATUS_START           = 1;
    public static final int DOWNLOAD_STATUS_FINISHED_ERROR  = 2;
    public static final int DOWNLOAD_STATUS_FINISHED_OKAY   = 3;
    public static final int DOWNLOAD_STATUS_STOP            = 4;
	
    public static final String EXTRA_CMD = "cmd"; //CMD_XXX
    public static final int CMD_UNKNOWN                     = 0;
    public static final int CMD_SEND_DEVICE_INFO            = 1;
    public static final int CMD_GET_OS_STATS_USER           = 10;
    public static final int CMD_GET_OS_STATS_ALL            = 11;
    public static final int CMD_GET_ROOTED_STATS_USER       = 12;
    public static final int CMD_GET_ROOTED_STATS_ALL        = 13;
    public static final int CMD_GET_TOP_DEVICE_STATS_MY     = 14;
    public static final int CMD_GET_TOP_DEVICE_STATS_ALL    = 15;
    public static final int CMD_GET_TOP_OPERATOR_STATS_MY   = 16;
    public static final int CMD_GET_TOP_OPERATOR_STATS_ALL  = 17;
    public static final int CMD_FIND_BY_CAMERA              = 50;
    public static final int CMD_FIND_BY_CPU                 = 51;
	
    private static final String EXTRA_HTTP_METHOD               = "hm";
    private static final String EXTRA_HTTP_URL                  = "url";
    private static final String EXTRA_HTTP_POST_DATA            = "pd";
    private static final String EXTRA_HTTP_IGNORE_CAHCED_RESULT = "icr";
	
	private static final int TIMEOUT_TIME = CONSTS.HTTP_TIMEOUT_TIME_IN_SECOND;
	
	private static final String URI_API = CONSTS.SERVER_API_URL;
	private static final String USER_AGENT = CONSTS.HTTP_USER_AGENT;
		
	private static int m_nCmdIsSending;
	private static LinkedList<HttpCmd> m_listPendingCmds = new LinkedList<HttpCmd>();
	
	private static ThreadSafeClientConnManager	m_mgrConn;
    private static DefaultHttpClient         	m_http;
    
	private static final String LOG_TAG = HttpConnSrvc.class.getSimpleName();
	
	/////////////////////////////////////////////
	public 
	HttpConnSrvc()
    {
		super(LOG_TAG);
    }

    public static synchronized boolean
    IsSending()
    {
        return m_nCmdIsSending != CMD_UNKNOWN;
    }
	
	public static synchronized void
	RequestSendDeviceInformation(Context ctx, String strDID, String strJSON)
	{
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;
	    if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "RequestSendDeviceInformation(): strDID=" + strDID);

	    //don't send if current device is emulator
	    if (!TextUtils.isEmpty(Build.PRODUCT) && 
            Build.PRODUCT.equalsIgnoreCase("sdk") &&
            !TextUtils.isEmpty(Build.MODEL) &&
            Build.MODEL.equalsIgnoreCase("sdk")) {
            if (aDebug.ENABLED)
                aDebug.d(LOG_TAG, "RequestSendDeviceInformation(): don't send emulator information to server!");            
            return;
        }            
        
	    //AES encrypt the json string
	    strJSON = AESEncryptor.Encrypt(strJSON, CONFIGURATION.DEVICE_INFO_ENCRYPT_KEY);
	    if (strJSON == null) {
	        if (aDebug.ENABLED)
                aDebug.d(LOG_TAG, "RequestSendDeviceInformation(): failed to encrypt JSON!");           
            return;
	    }
	    //if (aDebug.ENABLED)
        //    aDebug.d(LOG_TAG, "strJSON=" + strJSON);
	    
	    final String strClient = ctx.getPackageName();
	    
	    CRC32 crc32 = new CRC32();
	    crc32.update(strJSON.getBytes());
	    String strCRC32InHex = Long.toHexString(crc32.getValue());
	    
	    String strURL = URI_API + "device/";
        try {
            strURL += URLEncoder.encode(strDID, HTTP.UTF_8);
            strURL += ("?cs=" + strCRC32InHex);
            strURL += ("&rf=" + URLEncoder.encode(strClient, HTTP.UTF_8));
        } catch (UnsupportedEncodingException e) {
            strURL += strDID;
            strURL += ("?cs=" + strCRC32InHex);
            strURL += ("&rf=" + strClient);
        }
        
	    _AddPendingRequest(ctx, CMD_SEND_DEVICE_INFO, 
	                       HttpPost.METHOD_NAME, strURL, 
	                       strJSON, 
	                       false);
	}
	
	public static synchronized void
    RequestUserDefinedOsStats(Context ctx, int nCmd, String strMID, String strDID, boolean bIgnoreCachedResult)
    {
        _RequestOsStats(ctx, nCmd, strMID, strDID, bIgnoreCachedResult);
    }
	
	public static synchronized void
    RequestAllOsStats(Context ctx, String strDID, boolean bIgnoreCachedResult)
    {
	    _RequestOsStats(ctx, CMD_GET_OS_STATS_ALL, CONSTS.ALL_MODEL_OR_COUNTRY, strDID, bIgnoreCachedResult);
    }
	
	private static synchronized void
    _RequestOsStats(Context ctx, int nCmd, String strMID, String strDID, boolean bIgnoreCachedResult)
    {
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;
	    if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "RequestOsStats(): strMID=" + strMID + ", bIgnoreCachedResult=" + bIgnoreCachedResult);
	    String strURL = URI_API + "stats/os/";
	    try {
	        strURL += URLEncoder.encode(strMID, HTTP.UTF_8);
            strURL += ("?did=" + URLEncoder.encode(strDID, HTTP.UTF_8));
	    } catch (UnsupportedEncodingException e) {
	        strURL += strMID;
            strURL += ("?did=" + strDID);
	    }
	    
	    _AddPendingRequest(ctx, nCmd, 
	                       HttpGet.METHOD_NAME, strURL, 
	                       null,
	                       bIgnoreCachedResult);
    }
	
	public static synchronized void
    RequestUserDefinedRootedStats(Context ctx, int nCmd, String strMID, String strDID, boolean bIgnoreCachedResult)
    {
	    _RequestRootedStats(ctx, nCmd, strMID, strDID, bIgnoreCachedResult);
    }
	
	public static synchronized void
    RequestAllRootedStats(Context ctx, String strDID, boolean bIgnoreCachedResult)
    {
	    _RequestRootedStats(ctx, CMD_GET_ROOTED_STATS_ALL, CONSTS.ALL_MODEL_OR_COUNTRY, strDID, bIgnoreCachedResult);
    }
	
	private static synchronized void
    _RequestRootedStats(Context ctx, int nCmd, String strMID, String strDID, boolean bIgnoreCachedResult)
    {
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "RequestRootedStats(): strMID=" + strMID + ", bIgnoreCachedResult=" + bIgnoreCachedResult);
        
        String strURL = URI_API + "stats/rooted/";
        try {
            strURL += URLEncoder.encode(strMID, HTTP.UTF_8);
            strURL += ("?did=" + URLEncoder.encode(strDID, HTTP.UTF_8));
        } catch (UnsupportedEncodingException e) {
            strURL += strMID;
            strURL += ("?did=" + strDID);
        }
        
        _AddPendingRequest(ctx, nCmd, 
                           HttpGet.METHOD_NAME, strURL, 
                           null,
                           bIgnoreCachedResult);
    }
	
	public static synchronized void
    RequestToDeviceOfMyCntyStats(Context ctx, String strCountry, String strDID, boolean bIgnoreCachedResult)
    {
	    _RequestTopDeviceStats(ctx, CMD_GET_TOP_DEVICE_STATS_MY, strCountry, strDID, bIgnoreCachedResult);
    }
    
    public static synchronized void
    RequestTopDeviceOfWorldStats(Context ctx, String strDID, boolean bIgnoreCachedResult)
    {
        _RequestTopDeviceStats(ctx, CMD_GET_TOP_DEVICE_STATS_ALL, CONSTS.ALL_MODEL_OR_COUNTRY, strDID, bIgnoreCachedResult);
    }
    
    private static synchronized void
    _RequestTopDeviceStats(Context ctx, int nCmd, String strCountry, String strDID, boolean bIgnoreCachedResult)
    {
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;        
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "RequestRootedStats(): strCountry=" + strCountry + ", bIgnoreCachedResult=" + bIgnoreCachedResult);
        
        String strURL = URI_API + "stats/top_dev/";
        try {
            strURL += URLEncoder.encode(strCountry, HTTP.UTF_8);
            strURL += ("?did=" + URLEncoder.encode(strDID, HTTP.UTF_8));
        } catch (UnsupportedEncodingException e) {
            strURL += strCountry;
            strURL += ("?did=" + strDID);
        }
        
        _AddPendingRequest(ctx, nCmd, 
                           HttpGet.METHOD_NAME, strURL, 
                           null,
                           bIgnoreCachedResult);
    }
    
    public static synchronized void
    RequestTopOperatorOfMyCntyStats(Context ctx, String strCountry, String strDID, boolean bIgnoreCachedResult)
    {
        _RequestTopOperatorStats(ctx, CMD_GET_TOP_OPERATOR_STATS_MY, strCountry, strDID, bIgnoreCachedResult);
    }
    
    public static synchronized void
    RequestTopOperatorOfWorldStats(Context ctx, String strDID, boolean bIgnoreCachedResult)
    {
        _RequestTopOperatorStats(ctx, CMD_GET_TOP_OPERATOR_STATS_ALL, CONSTS.ALL_MODEL_OR_COUNTRY, strDID, bIgnoreCachedResult);
    }
    
    private static synchronized void
    _RequestTopOperatorStats(Context ctx, int nCmd, String strCountry, String strDID, boolean bIgnoreCachedResult)
    {
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;        
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "RequestRootedStats(): strCountry=" + strCountry + ", bIgnoreCachedResult=" + bIgnoreCachedResult);
        
        String strURL = URI_API + "stats/top_op/";
        try {
            strURL += URLEncoder.encode(strCountry, HTTP.UTF_8);
            strURL += ("?did=" + URLEncoder.encode(strDID, HTTP.UTF_8));
        } catch (UnsupportedEncodingException e) {
            strURL += strCountry;
            strURL += ("?did=" + strDID);
        }
        
        _AddPendingRequest(ctx, nCmd, 
                           HttpGet.METHOD_NAME, strURL, 
                           null,
                           bIgnoreCachedResult);
    }
    
    public static synchronized void
    FindTopDevicesByCamera(Context ctx, int nMinCameraPixels, int nMaxCameraPixels, 
                           String strDID, boolean bIgnoreCachedResult)
    {
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;        
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "FindTopDevicesByCamera(): nMinCameraPixels=" + nMinCameraPixels + ", nMaxCameraPixels=" + nMaxCameraPixels + ", bIgnoreCachedResult=" + bIgnoreCachedResult);
        
        String strURL = String.format("%sstats/feature/camera/%d/%d", URI_API, nMinCameraPixels, nMaxCameraPixels);
        try {
            strURL += ("?did=" + URLEncoder.encode(strDID, HTTP.UTF_8));
        } catch (UnsupportedEncodingException e) {
            strURL += ("?did=" + strDID);
        }
        
        _AddPendingRequest(ctx, CMD_FIND_BY_CAMERA, 
                           HttpGet.METHOD_NAME, strURL, 
                           null,
                           bIgnoreCachedResult);
    }
    
    public static synchronized void
    FindTopDevicesByCPU(Context ctx, int nMinCoreNum, int nMaxCoreNum,
                        int nMinSpeed, int nMaxSpeed,
                        String strDID, boolean bIgnoreCachedResult)
    {
        if (TextUtils.isEmpty(CONFIGURATION.SERVER_URL))
            return;        
        if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "FindTopDevicesByCPU(): nMinSpeed=" + nMinSpeed + ", nMaxSpeed=" + nMaxSpeed + ", nMinCoreNum" + nMinCoreNum + ", nMaxCoreNum + " + nMaxCoreNum + ", bIgnoreCachedResult=" + bIgnoreCachedResult);
        
        String strURL = String.format("%sstats/feature/cpu/%d/%d/%d/%d", URI_API, nMinSpeed, nMaxSpeed, nMinCoreNum, nMaxCoreNum);
        try {
            strURL += ("?did=" + URLEncoder.encode(strDID, HTTP.UTF_8));
        } catch (UnsupportedEncodingException e) {
            strURL += ("?did=" + strDID);
        }
        
        _AddPendingRequest(ctx, CMD_FIND_BY_CPU, 
                           HttpGet.METHOD_NAME, strURL, 
                           null,
                           bIgnoreCachedResult);
    }
    
	private static synchronized void
    _AddPendingRequest(Context ctx, 
                       int nCmd, String strHttpMethod, String strURL, 
                       String strPostData,
                       boolean bIgnoreCachedResult)
    {
	    if (nCmd == m_nCmdIsSending) {
	        if (aDebug.ENABLED)
                aDebug.d(LOG_TAG, "_AddPendingRequest(): is sending " + _GetCmdString(nCmd) + ", ignore this!");
            return;
	    }
	    for (HttpCmd cmd: m_listPendingCmds){
	        if (cmd.m_nCmd == nCmd) {
	            if (aDebug.ENABLED)
	                aDebug.d(LOG_TAG, "_AddPendingRequest(): a pending " + _GetCmdString(nCmd) + " exists in the queue, ignore this!");
	            return;
	        }
	    }
	    
	    HttpCmd cmd = new HttpCmd(nCmd, strHttpMethod, strURL, strPostData, bIgnoreCachedResult);
	    m_listPendingCmds.add(cmd);
	    
	    if (!IsSending())
	        _ProcessPendingRequests(ctx);
    }
	
	private static synchronized boolean
	_ProcessPendingRequests(Context ctx)
	{
	    if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "_ProcessPendingRequests()");
        
	    if (IsSending()) {
	        if (aDebug.ENABLED)
	            aDebug.e(LOG_TAG, "  IsSending() is true, don't call me for this case");
	        return false;
	    }
	    
	    if (m_listPendingCmds.size() <= 0) {
	        if (aDebug.ENABLED)
                aDebug.d(LOG_TAG, "  no pending cmds, return false");
	        return false;
	    }
	    
	    HttpCmd cmd = m_listPendingCmds.removeFirst();
	    final int nCmd = cmd.m_nCmd;
	    if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "  process cmd:" + _GetCmdString(nCmd));
	    
        m_nCmdIsSending = nCmd;

        Intent it = new Intent(ctx, HttpConnSrvc.class);
        it.putExtra(EXTRA_CMD, nCmd);
        it.putExtra(EXTRA_HTTP_METHOD, cmd.m_strHttpMethod);
        it.putExtra(EXTRA_HTTP_URL, cmd.m_strURL);
        it.putExtra(EXTRA_HTTP_IGNORE_CAHCED_RESULT, cmd.m_bIgnoreCachedResult);
        if (cmd.m_strPostData != null)
            it.putExtra(EXTRA_HTTP_POST_DATA, cmd.m_strPostData);
        ctx.startService(it);
        return true;
	}
	
	///////////////////////////////////////////////////////////////	
	private void
	_OnHandleIntentBegin(Intent it)
	{
	    if (aDebug.ENABLED)
	        aDebug.d(LOG_TAG, "_OnHandleIntentBegin()");
	}
	
	private void
    _OnHandleIntentEnd(Intent it)
    {   
	    if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "_OnHandleIntentEnd()");
        
	    m_nCmdIsSending = CMD_UNKNOWN;
        
        _ProcessPendingRequests(this);
    }
	
	private void
    _OnHandleIntent(Intent it)
    {
	    if (aDebug.ENABLED)
            aDebug.d(LOG_TAG, "_OnHandleIntent()");
	    
        Bundle bd = it.getExtras();
        if (bd == null) {
            if (aDebug.ENABLED)
                aDebug.e(LOG_TAG, "no bundle data!");
            return;
        }
        
        int nCmd = bd.getInt(EXTRA_CMD, CMD_UNKNOWN);
        if (nCmd == CMD_UNKNOWN) {
            if (aDebug.ENABLED)
                aDebug.e(LOG_TAG, "no cmd defined!");
            return;
        }
        
        final String strURL = it.getStringExtra(EXTRA_HTTP_URL);

        final boolean bUseCachedResult = !(it.getBooleanExtra(EXTRA_HTTP_IGNORE_CAHCED_RESULT, false));
        if (bUseCachedResult) { 
            if (_ReadFromCache(nCmd, strURL, true))
                return;
        }
        
        if (!_CheckConnectionAvailability(this)) { //no Internet
            if (_ReadFromCache(nCmd, strURL, false)) //return cache first
                return;
            
            _Notify(nCmd, DOWNLOAD_STATUS_START);
            _Notify(nCmd, DOWNLOAD_STATUS_FINISHED_ERROR, 
                    "Internet is not available"); //TODO: use R.string
            _Notify(nCmd, DOWNLOAD_STATUS_STOP);
            return;
        }

        _InitHttpClient();
        
        final String strHttpMethod = it.getStringExtra(EXTRA_HTTP_METHOD);
        
        switch (nCmd) {
        case CMD_SEND_DEVICE_INFO:
            _SendToServer(nCmd, strURL, strHttpMethod, 
                          bd.getString(EXTRA_HTTP_POST_DATA));
            break;
        case CMD_GET_OS_STATS_USER:
        case CMD_GET_OS_STATS_ALL:
        case CMD_GET_ROOTED_STATS_USER:
        case CMD_GET_ROOTED_STATS_ALL:
        case CMD_GET_TOP_DEVICE_STATS_MY:
        case CMD_GET_TOP_DEVICE_STATS_ALL:
        case CMD_GET_TOP_OPERATOR_STATS_MY:
        case CMD_GET_TOP_OPERATOR_STATS_ALL:
        case CMD_FIND_BY_CAMERA:
        case CMD_FIND_BY_CPU:
        default:
            _SendToServer(nCmd, strURL, strHttpMethod, null);
            break;
        }
    }
	
	@Override protected void 
	onHandleIntent(Intent it)
    {
        if (aDebug.ENABLED) {
            aDebug.d(LOG_TAG, "onHandleIntent()");
        }
        
        _OnHandleIntentBegin(it);
        _OnHandleIntent(it);
        _OnHandleIntentEnd(it);
    }
	
	private void
	_SendToServer(int nCmd, String strURL, String strHttpMethod, String strPostData)
	{		
		_Notify(nCmd, DOWNLOAD_STATUS_START);
		
		if (aDebug.ENABLED) {
        	aDebug.d(LOG_TAG, "_SendToServer()");
        	aDebug.d(LOG_TAG, "  nCmd=" + _GetCmdString(nCmd));
        	aDebug.d(LOG_TAG, "  strURL=" + strURL);
        	aDebug.d(LOG_TAG, "  strHttpMethod=" + strHttpMethod);
        	aDebug.d(LOG_TAG, "  strPostData len=" + (strPostData == null ? 0 : strPostData.length()));
        	aDebug.d(LOG_TAG, "  strPostData=" + (strPostData == null ? "null" : strPostData));
        }
		
		HttpPost post = null;
		HttpGet get = null;
        HttpEntity entity = null;
        
        try {
        	HttpResponse rp;
        	if (strHttpMethod.equals(HttpPost.METHOD_NAME)) {
        		post = new HttpPost(strURL);
        		if (strPostData != null) {
                	StringEntity entityIn = new StringEntity(strPostData, HTTP.UTF_8);
                    entityIn.setContentType("application/json");
                    post.setEntity(entityIn);
        		}
        		rp = m_http.execute(post, new BasicHttpContext());
        	}
        	else {
        		get = new HttpGet(strURL);
        		rp = m_http.execute(get, new BasicHttpContext());
        	}
        	
        	int nStatusCode = rp.getStatusLine().getStatusCode();
    		if (aDebug.ENABLED)
    			aDebug.d(LOG_TAG, "  server return status code: " + nStatusCode);
    			
        	if (nStatusCode == HttpStatus.SC_OK) {
        		
        		entity = rp.getEntity();
        		
        		String strContentType = _GetContentType(rp);
                String strContentEncoding = _GetContentEncoding(rp);
                long lContentLen = entity.getContentLength();
                
                if (aDebug.ENABLED) {
                	aDebug.d(LOG_TAG, "  response strContentType=" + strContentType);
                	aDebug.d(LOG_TAG, "  response strContentEncoding=" + strContentEncoding);
                	aDebug.d(LOG_TAG, "  response lContentLen=" + lContentLen);
                }
                
            	boolean bContentIsJSON;
                if (strContentType != null &&
                	strContentType.contains("application/json")) //"text/plain"
                	bContentIsJSON = true;
                else
                	bContentIsJSON = false;
                
                if (bContentIsJSON) {
                    String strResult = EntityUtils.toString(entity);
                    if (aDebug.ENABLED) {
                    	aDebug.d(LOG_TAG, "==================");
                    	aDebug.d(LOG_TAG, "strResult=" + strResult);
                    	aDebug.d(LOG_TAG, "==================");
                    }
                    JSONObject json = new JSONObject(strResult);
                    if (!json.has("rc") || json.getInt("rc") < 0) {
                        _Notify(nCmd, DOWNLOAD_STATUS_FINISHED_ERROR, strResult);
                    }
                    else {
                        _SaveToCache(nCmd, strURL, strResult);
                        _Notify(nCmd, DOWNLOAD_STATUS_FINISHED_OKAY, strResult);
                    }
                }
                else {
                	if (aDebug.ENABLED) {
                    	aDebug.d(LOG_TAG, "  return content is not JSON format");
                    	String strResult = EntityUtils.toString(entity);
                        if (aDebug.ENABLED) {
                            aDebug.d(LOG_TAG, "==================");
                            aDebug.d(LOG_TAG, "strResult=" + strResult);
                            aDebug.d(LOG_TAG, "==================");
                        }
                	}
                	_Notify(nCmd, DOWNLOAD_STATUS_FINISHED_ERROR, 
                	        "returned content is not in JSON format"); //TODO: use R.string.
                }
        	}
        	else {
        		_Notify(nCmd, DOWNLOAD_STATUS_FINISHED_ERROR, 
        		        "server return error: " + nStatusCode); //TODO: use R.string.
        	}
        }
        catch (SocketTimeoutException e) {
            if (aDebug.ENABLED)
            	aDebug.d(LOG_TAG, "_SendToServer()", e);
            _Notify(nCmd, DOWNLOAD_STATUS_FINISHED_ERROR,
                    e.getMessage()); //TODO: use R.string.
        }
        catch (Exception e) {
            if (aDebug.ENABLED)
            	aDebug.d(LOG_TAG, "_SendToServer()", e);
            _Notify(nCmd, DOWNLOAD_STATUS_FINISHED_ERROR,
                    e.getMessage()); //TODO: use R.string.
        } 
        finally {
            try {
                if (post != null)
                    post.abort();
                if (get != null)
                	get.abort();
                if (entity != null)
                    entity.consumeContent();
            }
            catch (Exception e) {
                if (aDebug.ENABLED)
                	aDebug.e(LOG_TAG, "_SendToServer()", e);
            }
        }
        
        _Notify(nCmd, DOWNLOAD_STATUS_STOP);
	}
	
	private boolean
	_UseCache(int nCmd)
	{
        /*switch (nCmd) {
        case CMD_GET_OS_STATS_MY:
        case CMD_GET_OS_STATS_ALL:
        case CMD_GET_ROOTED_STATS_MY:
        case CMD_GET_ROOTED_STATS_ALL:
        case CMD_GET_TOP_DEVICE_STATS_MY:
        case CMD_GET_TOP_DEVICE_STATS_ALL:
        case CMD_GET_TOP_OPERATOR_STATS_MY:
        case CMD_GET_TOP_OPERATOR_STATS_ALL:
            return true;
        }
        return false;
        */
        if (nCmd == CMD_SEND_DEVICE_INFO || nCmd == CMD_UNKNOWN)
            return false;
        return true;
	}
	
	private boolean
	_ReadFromCache(int nCmd, String strURL, boolean bCheckfExpired)
	{
        if (!_UseCache(nCmd)) return false;
        
        HttpCacheMgr mgrCache = HttpCacheMgr.GetInstance(this);
        String strResult = mgrCache.GetCacheData(strURL, bCheckfExpired);
        if (strResult != null) {
            if (aDebug.ENABLED)
                aDebug.d(LOG_TAG, "_ReadFromCache(): return cached data!");

            _Notify(nCmd, DOWNLOAD_STATUS_START);
            _Notify(nCmd, DOWNLOAD_STATUS_FINISHED_OKAY, strResult);
            _Notify(nCmd, DOWNLOAD_STATUS_STOP);
            return true;
        }
        return false;
	}
	
    private void
	_SaveToCache(int nCmd, String strURL, String strResult)
	{
	    if (!_UseCache(nCmd)) return;
	    
	    HttpCacheMgr mgrCache = HttpCacheMgr.GetInstance(this);
        mgrCache.SaveCacheData(strURL, strResult);
	}
	
	private void
    _InitHttpClient()
    {
		if (m_http != null)
			return;
			
        HttpParams params = new BasicHttpParams();
        ConnManagerParams.setMaxTotalConnections(params, 10);
        HttpConnectionParams.setConnectionTimeout(params, TIMEOUT_TIME * 1000);
        HttpConnectionParams.setSoTimeout(params, TIMEOUT_TIME * 1000);
        HttpConnectionParams.setStaleCheckingEnabled(params, false);
        HttpConnectionParams.setSocketBufferSize(params, 8192);
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
        HttpProtocolParams.setUserAgent(params, USER_AGENT);
        
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(
                new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(
            	new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        
        m_mgrConn = new ThreadSafeClientConnManager(params, schemeRegistry);
        
        m_http = new DefaultHttpClient(m_mgrConn, params);
        m_http.addRequestInterceptor(new HttpRequestInterceptor() {            
            public void process(final HttpRequest request, 
                                final HttpContext context)
                    throws HttpException, IOException {
                if (!request.containsHeader("Accept-Encoding")) {
                    if (aDebug.ENABLED) {
                        aDebug.d(LOG_TAG, "add Accept-Encoding: gzip, deflate");
                    }
                    request.addHeader("Accept-Encoding", "gzip, deflate");
                }
            }
        });
        m_http.addResponseInterceptor(new HttpResponseInterceptor() {
            public void process(final HttpResponse response, 
                                final HttpContext context) 
                    throws HttpException, IOException {
                Header ceHeader = response.getEntity().getContentEncoding();
                if (ceHeader != null) {
                    HeaderElement[] codecs = ceHeader.getElements();
                    if (codecs != null) {
                        if (aDebug.ENABLED) {
                            for (int i = 0; i < codecs.length; i++) {
                                aDebug.d(LOG_TAG, "codecs=" + codecs[i].getName());
                            }
                        }
                        for (int i = 0; i < codecs.length; i++) {
                            if (codecs[i].getName().equalsIgnoreCase("gzip")) {
                                response.setEntity(
                                        new GzipDecompressingEntity(response.getEntity())); 
                                return;
                            }
                        }
                    }
                }
            }
        });
        
        m_http.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(3, true));
    }
	
	private String
    _GetContentType(HttpResponse rp)
    {
        Header header = rp.getEntity().getContentType();
        if (header == null) return null;
        return header.getValue().toLowerCase();
    }
        
    private String
    _GetContentEncoding(HttpResponse rp)
    {
        Header header = rp.getEntity().getContentEncoding();
        if (header == null) return null;
        return header.getValue().toLowerCase();
    }
    
	private void
	_Notify(int nCmd, int nStatus)
	{
		_Notify(nCmd, nStatus, null);
	}
	
	private void
	_Notify(int nCmd, int nStatus, String strRetrunDataOrErrMsg)
	{
		if (aDebug.ENABLED) {
			aDebug.d(LOG_TAG, "_Notify(): cmd=" + _GetCmdString(nCmd) + 
			         ", nStatus=" + _GetStatusString(nStatus) + 
			         ", strRetrunDataOrErrMsg len=" + 
			         (strRetrunDataOrErrMsg == null ? 0 : strRetrunDataOrErrMsg.length()));
			if (nStatus == DOWNLOAD_STATUS_FINISHED_ERROR &&
			    strRetrunDataOrErrMsg != null)
			    aDebug.d(LOG_TAG, "_Notify(): error=" + strRetrunDataOrErrMsg);
		}
		
		Intent it = new Intent(ACTION_DOWNLOAD_STATUS);
		it.setPackage(getPackageName());
		it.putExtra(EXTRA_CMD, nCmd);
		it.putExtra(EXTRA_DOWNLOAD_STATUS, nStatus);
		if (strRetrunDataOrErrMsg != null)
			it.putExtra(EXTRA_DOWNLOAD_CONTENT, strRetrunDataOrErrMsg);		
		sendBroadcast(it);
	}
	
	private static String
	_GetCmdString(int nCmd)
	{
		switch(nCmd) {
		case CMD_SEND_DEVICE_INFO: return "send_device_info";
		case CMD_GET_OS_STATS_USER: return "get_user_os_stats";
        case CMD_GET_OS_STATS_ALL: return "get_all_os_stats";
        case CMD_GET_ROOTED_STATS_USER: return "get_user_rooted_stats";
        case CMD_GET_ROOTED_STATS_ALL: return "get_all_rooted_stats";
        case CMD_GET_TOP_DEVICE_STATS_MY: return "get_top_device_of_my_stats";
        case CMD_GET_TOP_DEVICE_STATS_ALL: return "get_top_device_of_world_stats";
        case CMD_GET_TOP_OPERATOR_STATS_MY: return "get_top_operator_of_my_stats";
        case CMD_GET_TOP_OPERATOR_STATS_ALL: return "get_top_operator_of_world_stats";
        
        case CMD_FIND_BY_CAMERA: return "find_top_devices_by_camera";
        case CMD_FIND_BY_CPU: return "find_top_devices_by_cpu";
        }
		return Integer.toString(nCmd);
	}
	
	private static String
	_GetStatusString(int nStatus)
	{
		switch(nStatus) {
		case DOWNLOAD_STATUS_START: return "start";
		case DOWNLOAD_STATUS_STOP: return "stop";
		case DOWNLOAD_STATUS_FINISHED_OKAY: return "finished_okay";
		case DOWNLOAD_STATUS_FINISHED_ERROR: return "finished_error";
		}
		return "unknown";
	}
	
	public static boolean
    IsConnectionAvailable(Context ctx)
    {
    	return _CheckConnectionAvailability(ctx);
    }
	
	private static boolean
    _CheckConnectionAvailability(Context ctx)
    {
    	ConnectivityManager cm = (ConnectivityManager)
    		ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
    	NetworkInfo info = cm.getActiveNetworkInfo();
    	if (info == null || !info.isConnected()) {
    		if (aDebug.ENABLED)
    			aDebug.d(LOG_TAG, "_CheckConnectionAvailability(): return false");
    	    return false;
    	}
    	return true;
    } 
	
	///////////////////////////////////////////////////////////////////
    private static class GzipDecompressingEntity extends HttpEntityWrapper {
        public 
        GzipDecompressingEntity(final HttpEntity entity) 
        {
            super(entity);
            //if (aDebug.ENABLED)
            //    aDebug.d(LOG_TAG, "GzipDecompressingEntity()");
        }
    
        @Override public final InputStream 
        getContent()
            throws IOException, IllegalStateException 
        {
            if (aDebug.ENABLED)
                aDebug.d(LOG_TAG, "GzipDecompressingEntity::getContent()");
            InputStream wrappedin = wrappedEntity.getContent();
            return new GZIPInputStream(wrappedin);
        }

        @Override public final long 
        getContentLength() 
        {
            return -1;
        }
    }
}
